#include <string.h>

#include "lib.h"

#include "ThreeCardRummyHand.h"
#include "cardmgr.h"
#include "tcpranks.h"

#define SELF	ThreeCardRummyHand
#define SUPER	Hand


// this array holds the point values for each card rank, with ace being at 0 and king being at 12.
uint8 ThreeCardRummyHand::m_anPoints[13] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     ThreeCardRummyHand::ThreeCardRummyHand(CardMgr* Mgr)
///
/// @param  Mgr             (in)  pointer to global card manager object
///
///////////////////////////////////////////////////////////////////////////////////////////////////
ThreeCardRummyHand::ThreeCardRummyHand(CardMgr* Mgr)
	: Hand(Mgr)
{
	m_nHandRank = 0xff;
    m_nScore=0;
}



///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     int32 ThreeCardRummyHand::outRank(uint8* Buf)
///
/// Encodes the rank of the current hand (e.g., pair, triple, 2 card straight, etc).
///
/// @param  Buf     (in/out) The buffer we want to store our rank in.
///
/// @return int32   always returns 1...the amount of data written to the buffer.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
int32
ThreeCardRummyHand::outRank(uint8* Buf)
{
	*Buf = (uint8)(m_nHandRank & 0xff);
	return 1;
}


///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     int32 ThreeCardRummyHand::outScore(uint8* Buf)
///
/// Encodes the point value of the current hand into the output buffer
///
/// @param  Buf     (in/out) The buffer we want to store our score in.
///
/// @return int32   always returns 1...the amount of data written to the buffer.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
int32
ThreeCardRummyHand::outScore(uint8* Buf)
{
    *Buf = (uint8)(m_nScore & 0xff);

	return 1;
}



///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     bool8 ThreeCardRummyHand::qualifies()
///
/// Returns true if this hand meets the minimum dealer qualification for this game (i.e., the
/// dealer must have a hand score of 20 or less).
///
/// @return bool8   returns true if the hand has a score value of 20 or less.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 ThreeCardRummyHand::qualifies()
{
    bool8 bRetVal = false;
	if(m_nScore<21)
    {
		bRetVal = true;
    }

    return bRetVal;
}




///////////////////////////////////////////////////////////////////////////////////////////////////
/// @fn     bool8 ThreeCardRummyHand::rankHand()
///
/// Figures out what the point score is for this hand, and also assigns the hand a "type", e.g.,
/// pair, triple, 2 card straight, etc.  The results can be accessed via the "getRank" and 
/// "getScore" methods.
///
/// @return bool8   returns true if successful, false otherwise.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
bool8 ThreeCardRummyHand::rankHand()
{
	uint8 rank;   

    m_nHandRank = TCR_BASE; // start with the assumption that we just have 3 unrelated cards
    m_nScore    = 0;        // and zero out the hand's score...it'll be recalculated below.

    int nNumCards = getCount();

    uint8 acCounts[13];		// A:index 0, 2:index 1, 3:index 2 ... K:index 12
	memset(acCounts, 0, 13 * sizeof(uint8));

    uint8 acSuits[4];
    memset(acSuits, 0, 4 * sizeof(uint8));


    // figure out how many cards we have of each rank, and how many of each suit
	for (int nI=0; nI<nNumCards; nI++)
	{
        uint8 nCard = getCardAt(nI);
		rank = mgr->getRank(nCard);

		if (rank >= 2)
		{
            // increment appropriate rank counter.
            if(rank!=CardMgr::ACE)
            {
			    ++acCounts[rank - 1];
            }
            else
            {
                ++acCounts[0];
            }

            // increment appropriate suit counter.
            ++acSuits[(mgr->getSuit(nCard)>>4) - 1];
		}
	}

    // do we have 3 of a kind?
	if (memchr(acCounts, 3, 13))
	{
        // yep.  This gives a score of 0.  Can't get any lower than that, so just return.
		m_nHandRank = TCR_3OFAKIND;
        m_nScore=0;
		return true;
	}
	
    // how about 2 of a kind?
	if (memchr(acCounts, 2, 13))
	{
        // Yep.  We have a pair.  Note that we still have to check and make sure 
        // that we don't also have a 2 card straight flush with a lower point 
        // score than this pair, so this isn't the end.

		m_nHandRank = TCR_PAIR;

        // Figure out the rank of the card that's not part of the pair
        // and that will be the point score for this entire hand.  Cards
        // that are part of a pair don't count towards the total point score 
        // of a hand.

        for(int nI=0;nI<13;++nI)
        {
            if(acCounts[nI]==1)
            {
                // This is our non-paired card.
                m_nScore += m_anPoints[nI];
                break;
            }
        }
	}
    else
    {
        // we didn't have a pair OR a triple, so figure out the score
        // with all 3 cards added in.  This will be our final answer IF there are
        // no straight flushes.  
        m_nHandRank = TCR_BASE;
        m_nScore = 0;

        for (int nZ=0; nZ<nNumCards; ++nZ)
        {
            uint8 nCard = getCardAt(nZ);
            uint8 nRank = mgr->getRank(nCard);

            if(nRank == CardMgr::ACE)
            {
                nRank=1;
            }

            m_nScore += m_anPoints[nRank-1];
        }
    }

    // SO, now let's go through looking for 2 and 3 card straight flushes.
    uint8 nTempScore = 0;
    uint8 nTempRank  = TCR_BASE;
    for(nI=0;nI<4;++nI)     // check counts for each suit
    {        
        if(acSuits[nI]>1)   // more than 1 card of this suit!  Do the cards make a straight?
        {
            uint8 nCurrSuit = (nI+1)<<4;

            // Check the rank counts to see if two or 3 consecutive cards of the current suit exist.
            // On every pass we'll be looking at nJ, nJ+1, and maybe nJ+2 (depending on whether nJ+2 
            // is past the end of the array).
            for(int nJ=0;nJ<12;++nJ)
            {
                if(acCounts[nJ]>0 && acCounts[nJ+1]>0)  // if 2 adjacent ranks have counts of 1 or more...
                {
                    bool8 bFound1 = false;  // was the card with nJ rank of the needed suit to make this a straight flush?
                    bool8 bFound2 = false;  // was the card with nJ+1 rank of the needed suit to make this a straight flush?
                    bool8 bFound3 = false;  // was the card with nJ+2 rank of the needed suit to make this a straight flush?

                    uint8 nPoints1 = 0; // if the card with nJ rank was of the correct suit, this variable holds its point value for later use.
                    uint8 nPoints2 = 0; // if the card with nJ+1 rank was of the correct suit, this variable holds its point value for later use.
                    uint8 nPoints3 = 0; // if the card with nJ+2 rank was of the correct suit, this variable holds its point value for later use.

                    bool8 bAce = false;

                    nTempScore = 0;     // kharmon.  02-15-06.  #9552.  Reset this to zero...it may have some left over stuff from a previous pass.

                    // is a three card straight flush possible?  ONLY if nJ isn't on the queen rank (11)
                    // and if the rank at nJ+2 has a count of 1.  So, set bCheck3 to true if it's possible and
                    // we should be checking for a 3 card run.
                    bool8 bCheck3 = nJ<11 && acCounts[nJ+2]>0;  

                    // Go through all the cards in the hand (should be 3) and see
                    // if the cards with ranks of nJ, nJ+1, and POSSIBLY nJ+2
                    // are of the correct suit to make a 2 or 3 card straight flush.
                    for (int nZ=0; nZ<nNumCards; ++nZ)
                    {
                        uint8 nCard = getCardAt(nZ);
                        uint8 nRank = mgr->getRank(nCard);
                        uint8 nSuit = mgr->getSuit(nCard);

                        if(nRank == CardMgr::ACE)
                        {
                            nRank=1;
                        }

                        // go ahead and add this card's point value to the total
                        // points for the whole hand.  We'll need this later.
                        nTempScore += m_anPoints[nRank-1];

                        if((nRank-1)==nJ && nSuit==nCurrSuit)
                        {
                            // if this card's rank matches the rank at nJ, and if it has
                            // the right suit, then we've found the first card in the straight.
                            nPoints1 = m_anPoints[nRank-1];
                            bFound1 = true;

                            if(nRank==1)
                            {
                                // if this first card is an ACE, then we may have
                                // an A-2-3 straight flush, which is special.  Make a note.
                                bAce=true;
                            }
                        }
                        else if((nRank-1)==(nJ+1) && nSuit==nCurrSuit)
                        {
                            // if this card's rank matches the rank at nJ+1, and if it has
                            // the right suit, then we've found the second card in the straight.
                            nPoints2 = m_anPoints[nRank-1];
                            bFound2 = true;
                        }
                        else if(bCheck3 && (nRank-1)==(nJ+2) && nSuit==nCurrSuit)
                        {
                            // if this card's rank matches the rank at nJ+2, and if it has
                            // the right suit, then we've found the THIRD card in the straight.
                            nPoints3 = m_anPoints[nRank-1];
                            bFound3 = true;
                        }
                    }

                    if(bFound1 && bFound2)  // if we found at least a 2 card straight flush THEN:
                    {
                        // Subtract out the points of the cards that are part of the straight.
                        // Whatever is left is the point value of the hand.
                        nTempScore -= (nPoints1 + nPoints2 + nPoints3);

                        if(bFound3)  
                        {            
                            // we have a 3 card straight flush
                            if(bAce==false)
                            {
                                // it's just a regular 3 card straight flush.
                                nTempRank = TCR_3CARD_STRAIGHT;
                            }
                            else
                            {
                                // this is a SPECIAL "Ace Low", 3 card straight flush.
                                nTempRank = TCR_ACE_3CARD_STRAIGHT; 
                            }
                        }
                        else
                        {
                            // we found a 2 card straight flush
                            nTempRank = TCR_2CARD_STRAIGHT;
                        }

                        break;
                    }
                }
            }
        }
    }

    // NOW, figure out the lowest way to score this hand.
    if(nTempRank>TCR_BASE && m_nHandRank>TCR_BASE)
    {
        // we apparently have both a pair AND a 2 card straight flush...choose 
        // the one with the lowest score.
        if(m_nScore>=nTempScore)
        {
            m_nHandRank = nTempRank;
            m_nScore = nTempScore;
        }
    }
    else if(nTempRank>TCR_BASE)
    {
        // if we find either a 2 or 3 card straight flush but nothing else (no pairs or triples)
        // then we'll end up here.  Just take the "straight flush" settings.
        m_nHandRank = nTempRank;
        m_nScore = nTempScore;
    }

	return true;
}

